package eg.backend.base.repository;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class GenericsUtils {
	public static final Class<?> getGenericClass(final Class<?> clazz)
			throws IllegalArgumentException {
		return getGenericClass(clazz, 0);
	}

	public static final Class<?> getGenericClass(final Class<?> clazz,
			final int index) throws IllegalArgumentException,
			IndexOutOfBoundsException {
		return getGenericClass(clazz, new GenericClassGetter() {
			public Class<?> getGenericClass(ParameterizedType parameterizedType) {
				Type[] params = parameterizedType.getActualTypeArguments();
				if (index >= params.length || index < 0) {
					throw new IndexOutOfBoundsException("Index: " + index
							+ ", Size of Parameterized Type: " + params.length);
				}

				if (!(params[index] instanceof Class)) {
					throw new IllegalArgumentException(
							"The class must be Parameterized");
				}

				Class<?> genericClass = (Class<?>) params[index];
				
				return genericClass;
			}
		});
	}

	public static Class<?> getGenericClass(final Class<?> clazz,
			final String name) {
		return getGenericClass(clazz, new GenericClassGetter() {
			@SuppressWarnings("unchecked")
			public Class<?> getGenericClass(ParameterizedType parameterizedType) {
				TypeVariable[] typeParameters = ((Class) parameterizedType
						.getRawType()).getTypeParameters();
				for (int i = 0; i < typeParameters.length; i++) {
					if (name.equals(typeParameters[i].getName())) {
						return (Class<?>) parameterizedType
								.getActualTypeArguments()[i];
					}
				}
				return null;
			}
		});
	}

	private static Class<?> getGenericClass(final Class<?> clazz,
			GenericClassGetter getter) {

		Class<?> superClass = clazz;
		Type genType = clazz.getGenericSuperclass();

		while (!(genType instanceof ParameterizedType)) {
			superClass = superClass.getSuperclass();
			if (superClass == null || superClass == Object.class) {
				break;
			}

			genType = superClass.getGenericSuperclass();
		}

		if (!(genType instanceof ParameterizedType)) {
			Type[] genTypes = clazz.getGenericInterfaces();
			for (Type type : genTypes) {
				if (type instanceof ParameterizedType) {
					genType = type;
				}
			}
		}

		if (!(genType instanceof ParameterizedType)) {
			throw new IllegalArgumentException(
					"The class must be Parameterized");
		}

		ParameterizedType parameterizedType = ((ParameterizedType) genType);

		return getter.getGenericClass(parameterizedType);
	}
	
	private static interface GenericClassGetter {
		Class<?> getGenericClass(ParameterizedType type);
	}
}
